using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace LambdaGp.Core
{
    public class Population
    {
        private static int _runningTasks;
        private readonly ManualResetEvent _computeFinessCompleted = new ManualResetEvent(false);
        private double _previousBestFitness;

        public Population()
        {
            PopulationSize = 750;
            MaxGenerations = 100;
            Elitist = true;
            GenerationCount = 0;
            MutationPercent = 0.1;
            EphemeralMutationPercent = 0.05;
            TargetFitness = 0.1;
            ParsimonyPressure = 0.01;
            TournamentSize = 7;
            UseInParallel = true;
            GenomeDefinition = new GenomeDefinition();
            CurrentGeneration = new List<Individual>();
            PreviousGeneration = new List<Individual>();
        }

        public delegate double FitnessFunctionDelegate(Individual individual);
        public event EventHandler<PopulationEventArgs> AfterGeneration;
        public static Random Random { get { return ThreadSafeRandom.Random; } }
        public Action<Population> OnStartingNewRun { get; set; }
        public Action<Population> OnBeforeComputeFitness { get; set; }
        public FitnessFunctionDelegate FitnessFunction { get; set; }

        // Settings        
        public int PopulationSize { get; set; }
        public bool Elitist { get; set; }
        public int MaxGenerations { get; set; }
        public int TournamentSize { get; set; }
        public double MutationPercent { get; set; }
        public double EphemeralMutationPercent { get; set; }
        public double TargetFitness { get; set; }
        public double ParsimonyPressure { get; set; }

        // Info       
        public int IndividualsProcessed { get; private set; }
        public int GenerationCount { get; private set; }
        public GenomeDefinition GenomeDefinition { get; private set; }
        public List<Individual> CurrentGeneration { get; private set; }
        public List<Individual> PreviousGeneration { get; private set; }
        public Individual Champion { get { return CurrentGeneration.FirstOrDefault(); } }
        public bool TargetFitnessReached { get { return Champion != null && Champion.Fitness <= TargetFitness; } }
        public int IdenticalsMutated { get; private set; }
        public int IdenticalsMutatedFailed { get; private set; }
        public bool UseInParallel { get; set; }
        public bool Paused { get; set; }

        public void RunGeneration()
        {
            if (GenerationCount == 0 || GenerationCount >= MaxGenerations)
            {
                ResetPopulation();
            }
            else
            {
                BuildNewGenerationBasedOnFitness();
            }

            ComputeFitness();
            GenerationCount++;
            DoAfterGeneration();
            _previousBestFitness = Champion.Fitness.Value;
        }

        public void BuildNewGenerationBasedOnFitness()
        {
            SwapGenerations();
            BuildCurrentGeneration();
        }

        public void ComputeFitness()
        {
            if (FitnessFunction == null)
            {
                throw new InvalidOperationException("No fitness function has been defined!");
            }
            if (OnBeforeComputeFitness != null)
            {
                OnBeforeComputeFitness(this);
            }
            ParallelForEach(CurrentGeneration, ComputeIndividualFitness);
            CurrentGeneration.Sort();
        }

        public void ResetPopulation()
        {
            if (OnStartingNewRun != null)
            {
                OnStartingNewRun(this);
            }
            GenerationCount = 0;
            CurrentGeneration.Clear();
            PreviousGeneration.Clear();
            AddToCurrentGeneration(PopulationSize, GenerateNewRandomIndividual);
            MutateIdenticalItems();
            FinalizePopulation();
        }

        public void Cross(Individual fatherChild, Individual mother)
        {
            GenomeDefinition.Cross(fatherChild.OperatorTree, mother.OperatorTree);
        }

        public void Mutate(Individual newIndividual)
        {
            if (Random.Next(100) > 30)
            {
                MacroMutate(newIndividual);
            }
            else
            {
                PointMutate(newIndividual);
            }
        }

        public void MutateIdenticalItems()
        {
            List<string> codes = new List<string>(PopulationSize);
            while (codes.Count < PopulationSize)
            {
                codes.Add(null);
            }

            ParallelFor(PopulationSize, i => codes[i] = CurrentGeneration[i].ToString());
            IdenticalsMutated = 0;
            IdenticalsMutatedFailed = 0;
            for (int i = 0; i < CurrentGeneration.Count; i++)
            {
                string firstCode = codes[i];
                if (firstCode == null)
                {
                    continue;
                }
                for (int j = i + 1; j < CurrentGeneration.Count; j++)
                {
                    string secondCode = codes[j];

                    if (secondCode == null)
                    {
                        continue;
                    }

                    if (firstCode.Equals(secondCode))
                    {
                        IdenticalsMutated++;
                        Mutate(CurrentGeneration[j]);
                        if (CurrentGeneration[j].ToString() == CurrentGeneration[i].ToString())
                        {
                            IdenticalsMutatedFailed++;
                        }
                        codes[j] = null;
                    }
                }
            }
        }

        public void MacroMutate(Individual individual)
        {
            GenomeDefinition.MacroMutate(individual.OperatorTree);
        }

        public void PointMutate(Individual individual)
        {
            GenomeDefinition.PointMutate(individual.OperatorTree);
        }

        public void EphemeralsMutate(Individual newIndividual)
        {
            GenomeDefinition.FunctionParameterMutate(newIndividual.OperatorTree);
        }

        public Individual GenerateNewRandomIndividual()
        {
            Individual individual = new Individual();
            individual.OperatorTree = GenomeDefinition.GenerateRandomGenome();
            return individual;
        }

        private void FinalizePopulation()
        {
            ParallelForEach(CurrentGeneration, individual => individual.Finalize(GenomeDefinition));
        }

        private void SwapGenerations()
        {
            List<Individual> temp = PreviousGeneration;
            PreviousGeneration = CurrentGeneration;
            CurrentGeneration = temp;
        }

        private void BuildCurrentGeneration()
        {
            CurrentGeneration.Clear();

            // Sort it, just in case someone from the outside is running this generation
            PreviousGeneration.Sort();
            if (Elitist)
            {
                AddBestFromPreviousGeneration();
            }

            List<Individual> topPerformers =
                PreviousGeneration
                    .Take((int)(PreviousGeneration.Count() * 0.3))
                    .ToList();

            AddMutantsToCurrentGeneration(topPerformers);
            AddEphemeralMutantsToCurrentGeneration(topPerformers);
            AddChildrenToCurrentGeneration(topPerformers);
            MutateIdenticalItems();
            FinalizePopulation();
        }

        private void AddMutantsToCurrentGeneration(IList<Individual> topPerformers)
        {
            AddToCurrentGeneration(
                (int)(MutationPercent * PopulationSize),
                () =>
                {
                    Individual newIndividual = TournamentSelection(topPerformers, TournamentSize).Clone();
                    Mutate(newIndividual);
                    return newIndividual;
                });
        }

        private void AddEphemeralMutantsToCurrentGeneration(IList<Individual> topPerformers)
        {
            if (GenomeDefinition.RequiresParameterMutation)
            {
                AddToCurrentGeneration(
                (int)(EphemeralMutationPercent * PopulationSize),
                () =>
                {
                    Individual newIndividual = TournamentSelection(topPerformers, 5).Clone();
                    EphemeralsMutate(newIndividual);
                    return newIndividual;
                });
            }
        }

        private void AddChildrenToCurrentGeneration(IList<Individual> topPerformers)
        {
            AddToCurrentGeneration(
                PopulationSize - CurrentGeneration.Count(),
                () =>
                {
                    Individual father = TournamentSelection(topPerformers, 5);
                    Individual mother = TournamentSelection(topPerformers, 5);

                    Individual child = father.Clone();
                    if (father != mother || ThreadSafeRandom.Random.NextDouble() < 0.30)
                    {
                        Cross(child, mother);
                    }
                    else
                    {
                        Mutate(child);
                    }

                    return child;
                });
        }

        private void AddToCurrentGeneration(int count, Func<Individual> func)
        {
            ParallelFor(
                count,
                i =>
                {
                    Individual individual = func();
                    AddToCurrentGeneration(individual);
                });
        }

        private void ParallelForEach<T>(IEnumerable<T> items, Action<T> action)
        {
            if (UseInParallel)
            {
                Parallel.ForEach(items, action);
            }
            else
            {
                items.ForEach(action);
            }
        }

        private void ParallelFor(int count, Action<int> action)
        {
            if (UseInParallel)
            {
                Parallel.For(0, count, action);
            }
            else
            {
                for (int i = 0; i < count; i++)
                {
                    action(i);
                }
            }
        }

        private void AddToCurrentGeneration(Individual individual)
        {
            lock (CurrentGeneration)
            {
                CurrentGeneration.Add(individual);
            }
        }

        private Individual TournamentSelection(IList<Individual> individuals, int tournamentSize)
        {
            Individual bestSoFar = null;
            for (int i = 0; i < tournamentSize; i++)
            {
                int pos = Random.Next(individuals.Count());
                Individual test = individuals[pos];
                if (bestSoFar == null || test.Fitness < bestSoFar.Fitness)
                {
                    bestSoFar = test;
                }
            }

            return bestSoFar;
        }

        private void AddBestFromPreviousGeneration()
        {
            if (PreviousGeneration.Count > 0)
            {
                Individual elite = PreviousGeneration.First(); // BottomItem(ind => ind.Fitness.Value);
                Individual newElite = elite.Clone();
                CurrentGeneration.Add(newElite);
            }
        }

        private void DoAfterGeneration()
        {
            if (AfterGeneration != null)
            {
                PopulationEventArgs args =
                    new PopulationEventArgs
                    {
                        Champion = Champion,
                        GenerationCount = GenerationCount,
                        DifferentFromPreviousGeneration = GenerationCount == 0 || _previousBestFitness != Champion.Fitness,
                        TargetFitnessReached = TargetFitnessReached
                    };

                AfterGeneration(this, args);
            }
        }

        private void ComputeIndividualFitness(Individual ind)
        {
            try
            {
                while (Paused)
                {
                    Thread.Sleep(100);
                }
                lock (this)
                {
                    IndividualsProcessed++;
                }
                double fitness = FitnessFunction(ind);
                if (double.IsNaN(fitness))
                {
                    fitness = 10e8;
                }
                else if (fitness < 0)
                {
                    throw new InvalidOperationException("Fitness must greater than or equal to zero!");
                }

                ind.FitnessBeforeParsimony = fitness;
                fitness += ParsimonyPressure * ind.Size;
                ind.Fitness = fitness;
            }
            finally
            {
                int tasksLeft = Interlocked.Decrement(ref _runningTasks);
                if (tasksLeft == 0)
                {
                    _computeFinessCompleted.Set();
                }
            }
        }
    }
}
